﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace Noris.Tools.FrxEditor.Editor
{
    #region class EditorUI : správce uživatelského rozhraní (menu, toolbary)
    /// <summary>
    /// EditorUI : správce uživatelského rozhraní (menu, toolbary).
    /// MainForm propůjčuje svoje menu a svoje ToolStripPanely, ale tento objekt (který je v okně instancován) řídí jak naplnění těchto vizuálních prvků, tak jejich konfiguraci a refresh podle aktuálního stavu dat.
    /// Tento objekt řeší i převod klávesových zkratek na Commandy.
    /// Podkladem s daty je úplný seznam Commandů (vytváří jej statická metoda EditorCommand.CreateAll(), seznam se ukládá v this.Commands.
    /// Dalším podkladem je konfigurace (soupis toolbarů, jejich obsah a umístění).
    /// Formulář si v případě potřeby vyžádá soupis UI controlů (menu, ToolStrip), třída EditorUI je vytvoří a vydá, formulář je zobrazí.
    /// V případě aktivity UI chodí eventy (z menu, z toolbarů) sem do EditorUI, tady se zpracují (najde se Command) a odsud se volá event UIAction. Ten by měl odebírat Dokument a předávat jej například do editoru.
    /// V opačném směru (aktualizace UI : enabled, checked, image, ...): tuto akci iniciuje Dokument = sestaví data o změnách UI, a pošle je do zdejší metody Refresh(). Zde se projdou naše UI controly a vloží se do nich přiměřené hodnoty.
    /// </summary>
    public class EditorUI
    {
        #region Konstrukce
        /// <summary>
        /// Konstruktor
        /// </summary>
        public EditorUI()
        {
            this._Init();
        }
        #endregion
        #region Příprava seznamu commandů. Vyhledání a vrácení příkazu podle klíče a podle klávesy
        /// <summary>
        /// Příprava celého objektu
        /// </summary>
        /// <returns></returns>
        private void _Init()
        {
            this._Commands = new Dictionary<EditorCommandKey, EditorCommand>();

            IEnumerable<EditorCommand> commands = EditorCommand.CreateAll();
            foreach (EditorCommand command in commands)
                this._AddCommand(command);
        }
        /// <summary>
        /// Přidá jeden command
        /// </summary>
        /// <param name="command"></param>
        private void _AddCommand(EditorCommand command)
        {
            this._Commands.Add(command.CommandKey, command);
        }
        /// <summary>
        /// Instanční seznam všech příkazů
        /// </summary>
        private Dictionary<EditorCommandKey, EditorCommand> _Commands;
        /// <summary>
        /// Vrátí Command pro daný klíč.
        /// Pokud takový příkaz neexistuje, vyhodí chybu.
        /// </summary>
        /// <param name="commandKey"></param>
        /// <returns></returns>
        public EditorCommand GetCommandByCommandKey(EditorCommandKey commandKey)
        {
            EditorCommand command;
            if (this._Commands.TryGetValue(commandKey, out command))
                return command;
            throw new InvalidProgramException("Command «" + commandKey.ToString() + "» does not exists in EditorCommands.");
        }
        /// <summary>
        /// Najde a vrátí Command pro danou klávesu.
        /// Pokud pro klávesu příkaz neexistuje, vrací null.
        /// </summary>
        /// <param name="keyModifiers">Přístupová klávesa, modifikátory (ctrl + alt + shift)</param>
        /// <param name="keyCode">Přístupová klávesa, kód bez modifikátorů</param>
        /// <returns></returns>
        public EditorCommand FindCommandForKeyCode(Keys keyModifiers, Keys keyCode)
        {
            foreach (EditorCommand command in this._Commands.Values)
            {
                if (command.IsActiveForKey(keyModifiers, keyCode))
                    return command;
            }
            return null;
        }
        /// <summary>
        /// Vrátí soupis příkazů editoru, keré vyhovují dané podmínce
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public IEnumerable<EditorCommand> FindCommands(Func<EditorCommand, bool> predicate)
        {
            if (predicate == null)
                return this._Commands.Values;
            return this._Commands.Values.Where(predicate);
        }
        #endregion
        #region Tvorba objektů UI
        /// <summary>
        /// Vytvoří objekty UI a umístí je do daných ToolStripů
        /// </summary>
        /// <param name="toolStripPanelTop"></param>
        /// <param name="toolStripPanelLeft"></param>
        /// <param name="toolStripPanelRight"></param>
        internal void CreateUI(ToolStripPanel toolStripPanelTop, ToolStripPanel toolStripPanelLeft, ToolStripPanel toolStripPanelRight)
        {
            this._ToolStripList = new List<EditorToolStrip>();

            this._CreateUIPanelPrepare(toolStripPanelTop);
            this._ToolStripPanelTop = toolStripPanelTop;

            this._CreateUIPanelPrepare(toolStripPanelLeft);
            this._ToolStripPanelLeft = toolStripPanelLeft;

            this._CreateUIPanelPrepare(toolStripPanelRight);
            this._ToolStripPanelRight = toolStripPanelRight;

            // Do ToolStrip panelů vytvořit a vložit jednotlivé ToolStripy, zatím se nebudou umísťovat:
            List<EditorCommandSet> toolList = this._CreateUIGetCommandSetList();
            foreach (EditorCommandSet toolInfo in toolList)
            {
                EditorToolStrip toolStrip = this._CreateUICreateToolStrip(toolInfo);
                this._ToolStripList.Add(toolStrip);
                this._CreateUIAddToPanel(toolStrip);
            }

            // Jednotlivé ToolStripy umístit na jejich pozice:
            foreach (EditorToolStrip toolStrip in this._ToolStripList)
                toolStrip.Location = toolStrip.CommandSet.ToolLocation;
            foreach (EditorToolStrip toolStrip in this._ToolStripList)
                toolStrip.LocationOk = true;
        }
        /// <summary>
        /// Metoda připraví k použití ToolStripPanel (ten může obsahovat více volně plovoucích toolstripů)
        /// </summary>
        /// <param name="toolStripPanel"></param>
        private void _CreateUIPanelPrepare(ToolStripPanel toolStripPanel)
        {
            if (toolStripPanel == null) return;
            toolStripPanel.Controls.Clear();
            toolStripPanel.RowMargin = new Padding(2);
        }
        /// <summary>
        /// Vytvoří a vrátí seznam s definicemi toolbarů použitých v systému.
        /// </summary>
        /// <returns></returns>
        private List<EditorCommandSet> _CreateUIGetCommandSetList()
        {
            List<EditorCommandSet> result = new List<EditorCommandSet>();

            // Načtení z konfigurace:

            // Fixní sestavení:
            result.Add(EditorCommandSet.CreateZoom());
            result.Add(EditorCommandSet.CreateEdit());
            result.Add(EditorCommandSet.CreateFormat());
            result.Add(EditorCommandSet.CreateDocument());
            result.Add(EditorCommandSet.CreateReportObject(this._Commands.Values));

            return result;
        }
        /// <summary>
        /// Vytvoří a vrátí control typu EditorToolStrip = toolbar, podle dané definice.
        /// Definice (vstupující parametr) obsahuje popisná data, výsledek metody je vizuální control.
        /// </summary>
        /// <param name="toolInfo"></param>
        /// <returns></returns>
        private EditorToolStrip _CreateUICreateToolStrip(EditorCommandSet toolInfo)
        {
            EditorToolStrip strip = new EditorToolStrip(toolInfo);
            strip.ToolbarAction += new EditorCommandExecuteHandler(this._ToolStrip_ToolbarAction);
            foreach (EditorCommandKey commandKey in toolInfo.Commands)
            {
                EditorCommand command = this.GetCommandByCommandKey(commandKey);
                if (command != null)
                    strip.AddButton(command);
            }
            return strip;
        }
        /// <summary>
        /// Metoda přidá daný toolstrip (=toolbar) do odpovídajícího panelu (Top / Left / Right).
        /// </summary>
        /// <param name="toolStrip"></param>
        private void _CreateUIAddToPanel(EditorToolStrip toolStrip)
        {
            if (toolStrip == null) return;
            ToolStripPanel panel = this._CreateUIGetPanelOnSide(toolStrip.InitialSide);
            if (panel == null) return;

            panel.Controls.Add(toolStrip);
        }
        /// <summary>
        /// Vrátí ToolStripPanel odpovídající dané straně
        /// </summary>
        /// <param name="location"></param>
        /// <returns></returns>
        private ToolStripPanel _CreateUIGetPanelOnSide(EditorToolLocation location)
        {
            ToolStripPanel panel = null;
            switch (location)
            {
                case EditorToolLocation.Top:
                    panel = this._ToolStripPanelTop;
                    break;
                case EditorToolLocation.Left:
                    panel = this._ToolStripPanelLeft;
                    break;
                case EditorToolLocation.Right:
                    panel = this._ToolStripPanelRight;
                    break;
            }
            return panel;
        }
        private ToolStripPanel _ToolStripPanelTop;
        private ToolStripPanel _ToolStripPanelLeft;
        private ToolStripPanel _ToolStripPanelRight;
        /// <summary>
        /// Soupis všech Toolstripů, které jsou v rámci UI vytvořeny
        /// </summary>
        private List<EditorToolStrip> _ToolStripList;
        #endregion
        #region Interaktivita: předání události z UI do eventu, aktualizace UI, řešení KeyEventů (podpora pro Controly, které dostávají KeyEventy a chtějí z nich získat Commandy)
        /// <summary>
        /// Událost, kdy uživatel prostřednictvím UI (Menu nebo Toolbar) žádá o provedení určité akce.
        /// UI požadavek zpracuje a do tohoto eventu předá již data o příkazu.
        /// Typicky tuto událost odebírá Document a řeší ji v sobě, popřípadě ve spolupráci s Editorem.
        /// </summary>
        public event EditorCommandExecuteHandler CommandExecute;
        /// <summary>
        /// Metoda, která pomáhá řešit klávesové eventy.
        /// Typický postup: Control, který má focus, dostává od Windows fyzické klávesové eventy.
        /// Aby je nemusel dekódovat sám (protože jejich klávesové zkratky mohou být konfigurovatelné !), využívá tuto metodu UI = předá ji informace o klávesách.
        /// Tato metoda zjistí, zda klávesy odpovídají některému commandu, pokud ano tak tento command provede (vytvoří Command a zavolá standardní event CommandExecute), a vrací se true = byl to příkaz.
        /// Pokud to není příkaz, pak se event nevolá a vrací se false (klávesa je nevyřešena, control si ji může řešit sám).
        /// </summary>
        /// <param name="keyModifiers"></param>
        /// <param name="keyCode"></param>
        /// <returns></returns>
        public bool KeyEventResolve(Keys keyModifiers, Keys keyCode)
        {
            EditorCommand command = this.FindCommandForKeyCode(keyModifiers, keyCode);
            if (command == null) return false;                   // Není to command.
            this.OnCommandExecute(new EditorCommandExecuteArgs(command.CommandKey, null));
            return true;                                         // Byl to command.
        }
        /// <summary>
        /// Aktualizuje jednotlivé vizuální prvky (položky menu / toolbaru) podle stavů předaných v parametru této metody.
        /// </summary>
        /// <param name="itemStates"></param>
        public void UIRefresh(IEnumerable<EditorUIItemState> itemStates)
        {
            // Vytvořím index předaných stavových položek:
            Dictionary<EditorCommandKey, EditorUIItemState> itemStateDict = new Dictionary<EditorCommandKey, EditorUIItemState>();
            foreach (EditorUIItemState itemState in itemStates)
            {
                if (itemStateDict.ContainsKey(itemState.CommandKey))
                    itemStateDict[itemState.CommandKey] = itemState;
                else
                    itemStateDict.Add(itemState.CommandKey, itemState);
            }

            // Aktualizuji všechny toolStripy (ez ohledu na to, kde zrovna jsou = na kterém ToolPanelu):
            foreach (EditorToolStrip toolStrip in this._ToolStripList)
                this._UIRefreshToolStrip(toolStrip, itemStateDict);

        }
        /// <summary>
        /// Aktualizuje všechny prvky daného toolstripu
        /// </summary>
        /// <param name="toolStrip"></param>
        /// <param name="itemStateDict"></param>
        private void _UIRefreshToolStrip(EditorToolStrip toolStrip, Dictionary<EditorCommandKey, EditorUIItemState> itemStateDict)
        {
            foreach (ToolStripItem toolItem in toolStrip.Items)
            {
                if (toolItem.Tag is EditorCommandKey)
                {
                    EditorCommandKey commandKey = (EditorCommandKey)toolItem.Tag;
                    EditorUIItemState itemState;
                    if (itemStateDict.TryGetValue(commandKey, out itemState))
                        itemState.RefreshItem(toolItem);
                }
            }
        }
        /// <summary>
        /// Zavolá event CommandExecute s předaným parametrem args
        /// </summary>
        /// <param name="args"></param>
        protected virtual void OnCommandExecute(EditorCommandExecuteArgs args)
        {
            if (this.CommandExecute != null)
                this.CommandExecute(this, args);
        }
        /// <summary>
        /// Privátní handler pro eventy na jednotlivých UI controlech
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void _ToolStrip_ToolbarAction(object sender, EditorCommandExecuteArgs args)
        {
            this.OnCommandExecute(args);
        }
        #endregion
    }
    #endregion
    #region class EditorUIItemState : třída, které předává data pro refresh UI (informace o tom, které příkazy jsou aktuálně dostupné/nedostupné, checked, Icons, atd)
    /// <summary>
    /// EditorUIItemState : třída, které předává data pro refresh UI (informace o tom, které příkazy jsou aktuálně dostupné/nedostupné, checked, Icons, atd).
    /// Třída rovněž řeší fyzickou aktualizaci jednotlivého objektu UI (Item, Button).
    /// </summary>
    public class EditorUIItemState
    {
        /// <summary>
        /// EditorUIItemState : třída, které předává data pro refresh UI (informace o tom, které příkazy jsou aktuálně dostupné/nedostupné, checked, Icons, atd).
        /// Konstruktor
        /// </summary>
        /// <param name="commandKey">Identifikace příkazu</param>
        public EditorUIItemState(EditorCommandKey commandKey)
        {
            this.CommandKey = commandKey;
        }
        /// <summary>
        /// EditorUIItemState : třída, které předává data pro refresh UI (informace o tom, které příkazy jsou aktuálně dostupné/nedostupné, checked, Icons, atd).
        /// Konstruktor
        /// </summary>
        /// <param name="commandType">Identifikace příkazu</param>
        /// <param name="commandSpecification">Specifikace příkazu</param>
        public EditorUIItemState(EditorCommandType commandType, object commandSpecification)
        {
            this.CommandKey = new EditorCommandKey(commandType, commandSpecification);
        }
        /// <summary>
        /// Identifikace příkazu (klíč)
        /// </summary>
        public EditorCommandKey CommandKey { get; private set; }
        /// <summary>
        /// Je položka Enabled?  (null = neměnit)
        /// </summary>
        public Boolean? Enabled { get; set; }
        /// <summary>
        /// Je položka Checked?  (null = neměnit)
        /// </summary>
        public Boolean? Checked { get; set; }
        /// <summary>
        /// Text položky (v menu) (null = neměnit)
        /// </summary>
        public String MenuText { get; set; }
        /// <summary>
        /// Tooltip položky (null = neměnit)
        /// </summary>
        public String Tooltip { get; set; }
        /// <summary>
        /// Aktualizuje podle svých dat data v prvku ToolStripItem
        /// </summary>
        /// <param name="toolItem"></param>
        internal void RefreshItem(ToolStripItem toolItem)
        {
            if (this.Enabled.HasValue)
                toolItem.Enabled = this.Enabled.Value;
            if (this.Tooltip != null)
                toolItem.ToolTipText = this.Tooltip;

            if (toolItem is ToolStripButton)
                this._RefreshButton(toolItem as ToolStripButton);
        }
        /// <summary>
        /// Aktualizuje podle svých dat data v prvku ToolStripButton, nad rámec toho co dělá metoda this.RefreshItem()
        /// </summary>
        /// <param name="toolStripButton"></param>
        private void _RefreshButton(ToolStripButton toolStripButton)
        {
            if (this.Checked.HasValue)
                toolStripButton.Checked = this.Checked.Value;
        }
    }
    #endregion
    #region class EditorToolStrip : třída toolbaru v editoru.  enum EditorToolLocation;  delegate EditorCommandExecuteHandler a class EditorCommandExecuteArgs
    /// <summary>
    /// EditorToolStrip : třída toolbaru v editoru
    /// </summary>
    public class EditorToolStrip : ToolStrip
    {
        #region Public property a eventy
        internal EditorToolStrip(EditorCommandSet commandSet)
        {
            this.CommandSet = commandSet;
            this.Key = commandSet.Key;
            this.Text = commandSet.Text;
            this.InitialSide = commandSet.ToolSide;
        }
        /// <summary>
        /// Reference na set, z něhož je tento toolbar vytvořen
        /// </summary>
        internal EditorCommandSet CommandSet { get; private set; }
        /// <summary>
        /// Klíčový název
        /// </summary>
        public string Key { get; private set; }
        /// <summary>
        /// Výchozí strana, na které bude tento toolstrip umístěn
        /// </summary>
        public EditorToolLocation InitialSide { get; set; }
        /// <summary>
        /// Event volaný při události, kdy toolbar vyvolává nějakou akci
        /// </summary>
        public event EditorCommandExecuteHandler ToolbarAction;
        #endregion
        #region Protected property AddButton(), AddSeparator(), interní eventhandler
        /// <summary>
        /// Přidá button podle definice commandu
        /// </summary>
        /// <param name="command">Objekt popisující command</param>
        internal void AddButton(EditorCommand command)
        {
            if (command == null) return;
            if (command.CommandType != EditorCommandType.Separator && command.ToolIconSmall == null) return;

            this.AddButton(command.CommandKey, command.ToolText, command.ToolTip, command.ToolIconSmall);
        }
        /// <summary>
        /// Přidá button nebo separátor, podle typu commandu
        /// </summary>
        /// <param name="commandKey">Druh příkazu</param>
        /// <param name="text"></param>
        /// <param name="toolTip"></param>
        /// <param name="image"></param>
        internal void AddButton(EditorCommandKey commandKey, string text, string toolTip, Image image)
        {
            if (commandKey.CommandType == EditorCommandType.Separator)
            {
                this.Items.Add(new ToolStripSeparator());
            }
            else
            {
                ToolStripButton button = new ToolStripButton(text, image);
                button.Tag = commandKey;
                button.ToolTipText = toolTip;
                button.AutoToolTip = true;
                button.DisplayStyle = ToolStripItemDisplayStyle.Image;
                button.ImageScaling = ToolStripItemImageScaling.None;
                button.Click += new EventHandler(_Button_Click);
                this.Items.Add(button);
            }
        }
        /// <summary>
        /// Interní event ButtonClick
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _Button_Click(object sender, EventArgs e)
        {
            if (sender != null && sender is ToolStripItem)
            {
                ToolStripItem item = sender as ToolStripItem;
                if (item.Tag != null && item.Tag is EditorCommandKey)
                {
                    EditorCommandKey commandKey = (EditorCommandKey)item.Tag;
                    this.OnAction(sender, commandKey, null);
                }
                else
                    throw new InvalidOperationException("Click na Toolitem, jehož Tag není EditorCommandKey.");
            }
        }
        /// <summary>
        /// Vyvolá event this.ToolbarAction
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="commandKey"></param>
        /// <param name="data"></param>
        protected virtual void OnAction(object sender, EditorCommandKey commandKey, object data)
        {
            if (this.ToolbarAction != null)
                this.ToolbarAction(sender, new EditorCommandExecuteArgs(commandKey, data));
        }
        /// <summary>
        /// Pokusí se umístit this toolstrip do parenta
        /// </summary>
        protected override void SetDisplayedItems()
        {
            base.SetDisplayedItems();
            if (this.CommandSet != null && !this.LocationOk)
            {
                this.Location = this.CommandSet.ToolLocation;
            }
        }
        /// <summary>
        /// Při změně pozice
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLocationChanged(EventArgs e)
        {
            base.OnLocationChanged(e);
            if (this.LocationOk)
                this.CommandSet.ToolLocation = this.Location;
        }
        /// <summary>
        /// Příznak, že umístění ToolStripu je již hotové, a od nastavení na true se sleduje změna Location. Před tím se nesleduje.
        /// </summary>
        internal bool LocationOk { get; set; }
        #endregion
    }
    #endregion
    #region enum EditorToolLocation;  delegate EditorCommandExecuteHandler a class EditorCommandExecuteArgs
    /// <summary>
    /// Umístění toolstripu
    /// </summary>
    public enum EditorToolLocation
    {
        /// <summary>
        /// Nikde (není vidět)
        /// </summary>
        None = 0,
        /// <summary>
        /// Horní pás
        /// </summary>
        Top,
        /// <summary>
        /// Levý sloupec
        /// </summary>
        Left,
        /// <summary>
        /// Pravý sloupec
        /// </summary>
        Right
    }
    /// <summary>
    /// Předpis pro handler události "Editor Toolbar - akce"
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    public delegate void EditorCommandExecuteHandler(object sender, EditorCommandExecuteArgs args);
    /// <summary>
    /// Data o události "Editor Toolbar - akce"
    /// </summary>
    public class EditorCommandExecuteArgs : EventArgs
    {
        /// <summary>
        /// Data o události "Editor Toolbar - akce". Konstruktor.
        /// </summary>
        /// <param name="commandKey"></param>
        public EditorCommandExecuteArgs(EditorCommandKey commandKey)
        {
            this.CommandKey = commandKey;
        }
        /// <summary>
        /// Data o události "Editor Toolbar - akce". Konstruktor.
        /// </summary>
        /// <param name="commandKey"></param>
        /// <param name="data"></param>
        public EditorCommandExecuteArgs(EditorCommandKey commandKey, object data)
        {
            this.CommandKey = commandKey;
            this.Data = data;
        }
        /// <summary>
        /// Data o události "Editor Toolbar - akce". Vizualizace.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.CommandKey.ToString() + (this.Data == null ? "" : " (" + this.Data.ToString() + ")");
        }
        /// <summary>
        /// Druh akce
        /// </summary>
        public EditorCommandKey CommandKey { get; private set; }
        /// <summary>
        /// Přidaná data
        /// </summary>
        public object Data { get; private set; }
    }
    #endregion
}
